package com.halden.TRPG.service.impl;

import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.halden.TRPG.common.CodeEnum;
import com.halden.TRPG.common.MyException;
import com.halden.TRPG.entity.DarkDiceMessageEntity;
import com.halden.TRPG.entity.MessageEntity;
import com.halden.TRPG.entity.vo.messageResponse.MessageResponseVo;
import com.halden.TRPG.entity.vo.messageResponse.Text;
import com.halden.TRPG.mapper.MessageMapper;
import com.halden.TRPG.service.MessageService;
import com.halden.TRPG.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.json.JsonObject;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

@Service
@Transactional(rollbackFor = MyException.class)
@Slf4j
public class MessageServiceImpl extends ServiceImpl<MessageMapper, MessageEntity> implements MessageService {

    @Autowired
    private UserService userService;

    @Autowired
    private ExecutorService fixedThreadPool;

    @Autowired
    private String staticPath;

    @Resource
    private RedisTemplate redisTemplate;

    @Value(("${on-path}"))
    private String onPath;

    private static final String MESSAGE_LIST_NAME = "messageList";

    @Autowired
    private MongoTemplate mongoTemplate;

    private MessageEntity parseJsonObjToMessageEntity(JSONObject jsonObject){
        MessageEntity messageEntity = new MessageEntity();
        try {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            messageEntity.setCreateTime(dateFormat.parse(jsonObject.getString("createTime")));
            messageEntity.setUid(jsonObject.getString("fromUserId"));
            messageEntity.setUsername(jsonObject.getString("username"));
            messageEntity.setMessage(jsonObject.getString("message"));
            messageEntity.setRid(jsonObject.getLong("rid"));
        } catch (Exception e){
            e.printStackTrace();
        }
        return messageEntity;
    }

    @Override
    public CodeEnum saveMessage(JSONObject jsonObject) {
        MessageEntity messageEntity = parseJsonObjToMessageEntity(jsonObject);
        boolean save = cacheMessageToRedis(messageEntity);
        if (save){
            return CodeEnum.SUCCESS;
        } else {
            throw new MyException(CodeEnum.OPTION_FAIL_EXCEPTION);
        }
    }

    @Override
    public boolean cacheMessageToRedis(MessageEntity entity) {
        ListOperations listOperations = redisTemplate.opsForList();
        String jsonString = JSON.toJSONString(entity);
        log.info(jsonString);
        String listName = MESSAGE_LIST_NAME + entity.getRid();
        Long cache = listOperations.leftPush(listName, jsonString);
        Long size = listOperations.size(listName);
        boolean flag = true;
        if (size > 50){
            Future<Boolean> saveMessages = fixedThreadPool.submit(() -> {
                List<MessageEntity> entities = new ArrayList<>();
                while (size > 0) {
                    String s = (String) listOperations.rightPop(listName);
                    JSONObject jsonObject = JSON.parseObject(s);
                    MessageEntity messageEntity = parseJsonObjToMessageEntity(jsonObject);
                    entities.add(messageEntity);
                }
                boolean f = this.saveMessagesToDB(entities);
                return f;
            });
            try {
                flag = saveMessages.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        return flag && (cache > 0);
    }

    @Override
    public boolean saveMessagesToDB(List<MessageEntity> entities) {
        int i = baseMapper.saveMessages(entities.get(0).getRid(), entities);
        return i > 0;
    }

    @Override
    public void saveMessageWhenClose(Long rid) {
        ListOperations listOperations = redisTemplate.opsForList();
        String listName = MESSAGE_LIST_NAME + rid;
        List<MessageEntity> entities = new ArrayList<>();
        Long size = listOperations.size(listName);
        log.info(size + "");
        while (size > 0) {
            String s = (String) listOperations.rightPop(listName);
            log.info(s);
            JSONObject jsonObject = JSON.parseObject(s);
            MessageEntity messageEntity = parseJsonObjToMessageEntity(jsonObject);
            messageEntity.setUid(jsonObject.getString("uid"));
            entities.add(messageEntity);
            size--;
        }
        log.info(entities.get(0).getUid());
        this.saveMessagesToDB(entities);
    }

    @Override
    public CodeEnum getMessage(String uid, Long rid, Long start, Long length) {
        List<MessageEntity> messageEntities = baseMapper.getMessages(rid,start,length);
        Collections.reverse(messageEntities);
        List<MessageResponseVo> responseVos = new ArrayList<>();
        Map<String,String> avatars = new HashMap<>();
        for (MessageEntity entity : messageEntities){
            if (!avatars.containsKey(entity.getUid())){
                CodeEnum codeEnum = userService.getAvatar(entity.getUid());
                if (codeEnum.getData() instanceof String){
                    avatars.put(entity.getUid(), (String) codeEnum.getData());
                }
            }
            MessageResponseVo vo = new MessageResponseVo();
            vo.setDate(entity.getCreateTime());
            vo.setName(entity.getUsername());
            vo.setText(new Text(entity.getMessage()));
            vo.setImg(avatars.get(entity.getUid()));
            if (uid.equals(entity.getUid())){
                vo.setMine(true);
            }
            responseVos.add(vo);
        }
        if (messageEntities.size() != 0){
            return CodeEnum.SUCCESS.setData(responseVos);
        } else {
            throw new MyException(CodeEnum.OPTION_FAIL_EXCEPTION);
        }
    }

    @Override
    public CodeEnum createMessageTable(Long rid) {
        MessageMapper baseMapper = getBaseMapper();
        int createTable = baseMapper.createMessageTable(rid);
        if (createTable == 0){
            return CodeEnum.SUCCESS;
        } else {
            throw new MyException(CodeEnum.OPTION_FAIL_EXCEPTION);
        }
    }

    @Override
    public boolean deleteMessages(Long rid) {
        int i = baseMapper.deleteMessages(rid);
        return i == 0;
    }

    @Override
    public CodeEnum downloadMessages(Long rid) {
        List<MessageEntity> allMessages = baseMapper.getAllMessages(rid);
        Criteria criteria = Criteria.where("_id").is(rid);
        Query query = new Query(criteria);
        DarkDiceMessageEntity darkDice = mongoTemplate.findOne(query, DarkDiceMessageEntity.class, "darkDice");
        List<MessageEntity> darkDiceMessages = darkDice.getDarkDiceMessages();
        int i = 0;
        int j = 0;
        while (i < allMessages.size() && j < darkDiceMessages.size()){
            if (allMessages.get(i).getCreateTime().equals(darkDiceMessages.get(j).getCreateTime())){
                allMessages.set(i,darkDiceMessages.get(j));
                j++;
            }
            i++;
        }
        String path = staticPath + "room/" + rid;
        File dir = new File(path);
        if (!dir.exists()){
            dir.mkdirs();
        }
        EasyExcel.write(path + "/messages.xlsx", MessageEntity.class).sheet("消息").doWrite(allMessages);
        String url = onPath + "room/" + rid + "/messages.xlsx";
        return CodeEnum.SUCCESS.setData(url);
    }

    @Override
    public CodeEnum saveDarkDice(JSONObject jsonObject) {
        MessageEntity messageEntity = new MessageEntity();
        try {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            messageEntity.setCreateTime(dateFormat.parse(jsonObject.getString("createTime")));
            messageEntity.setUid(jsonObject.getString("fromUserId"));
            messageEntity.setUsername(jsonObject.getString("username"));
            messageEntity.setMessage(jsonObject.getString("message"));
            messageEntity.setRid(jsonObject.getLong("rid"));
        } catch (Exception e){
            e.printStackTrace();
        }
        Criteria criteria = Criteria.where("_id").is(messageEntity.getRid());
        Query query = new Query(criteria);
        DarkDiceMessageEntity darkDice = mongoTemplate.findOne(query, DarkDiceMessageEntity.class, "darkDice");
        darkDice.getDarkDiceMessages().add(messageEntity);
        DarkDiceMessageEntity insert = mongoTemplate.save(darkDice, "darkDice");
        if (insert != null){
            return CodeEnum.SUCCESS;
        }else{
            throw new MyException(CodeEnum.OPTION_FAIL_EXCEPTION);
        }
    }

    @Override
    public CodeEnum getDarkDiceMessage(Long rid) {
        Criteria criteria = Criteria.where("_id").is(rid);
        Query query = new Query(criteria);
        DarkDiceMessageEntity darkDice = mongoTemplate.findOne(query, DarkDiceMessageEntity.class, "darkDice");
        if (Objects.isNull(darkDice)){
            return CodeEnum.SUCCESS.setData(new ArrayList<DarkDiceMessageEntity>());
        } else {
            String diceAvatar = (String)userService.getAvatar("dice").getData();
            List<MessageResponseVo> responseVos = darkDice.getDarkDiceMessages().stream().map(item -> {
                MessageResponseVo vo = new MessageResponseVo();
                vo.setName(item.getUsername());
                vo.setText(new Text(item.getMessage()));
                vo.setImg(diceAvatar);
                vo.setDate(item.getCreateTime());
                return vo;
            }).collect(Collectors.toList());
            return CodeEnum.SUCCESS.setData(responseVos);
        }
    }
}
